﻿using System.Collections;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Reflection;
using Devonline.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;

namespace Devonline.Database.PostgreSQL;

public static class ServiceExtensions
{
    /// <summary>
    /// 注册数据库上下文
    /// </summary>
    /// <param name="services">依赖注入服务容器</param>
    /// <param name="appSetting">基础配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddPostgreSQL<TDbContext>(this IServiceCollection services, AppSetting appSetting) where TDbContext : DbContext
    {
        if (!string.IsNullOrWhiteSpace(appSetting.ApplicationDbContext))
        {
            if (appSetting.DateTimeKind == DateTimeKind.Local)
            {
                // 用于启用或禁用 Npgsql 客户端与 Postgres 服务器之间的时间戳行为。它并不会直接修改 Postgres 的时区设置。
                AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
                AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
            }

            var assembly = appSetting.MigrationsAssembly ?? Assembly.GetCallingAssembly().FullName;
            services.AddDbContext<TDbContext>(builder => builder.UseNpgsql(appSetting.ApplicationDbContext, options => options.MigrationsAssembly(assembly)), appSetting.ServiceLifetime);
        }

        return services;
    }
    /// <summary>
    /// 注册数据库上下文
    /// </summary>
    /// <param name="services">依赖注入服务容器</param>
    /// <param name="connectionString">数据库连接字符串</param>
    /// <param name="migrationsAssembly">数据迁移作用的程序集</param>
    /// <param name="contextLifetime">数据库上下文的生命周期</param>
    /// <param name="dateTimeKind">当前使用的时区类型, 默认本地时间</param>
    /// <returns></returns>
    public static IServiceCollection AddPostgreSQL<TDbContext>(this IServiceCollection services, string? connectionString = default, string? migrationsAssembly = default, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, DateTimeKind dateTimeKind = DateTimeKind.Utc) where TDbContext : DbContext
    {
        if (!string.IsNullOrWhiteSpace(connectionString))
        {
            if (dateTimeKind == DateTimeKind.Local)
            {
                // 用于启用或禁用 Npgsql 客户端与 Postgres 服务器之间的时间戳行为。它并不会直接修改 Postgres 的时区设置。
                AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
                AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
            }

            var assembly = migrationsAssembly ?? Assembly.GetCallingAssembly().FullName;
            services.AddDbContext<TDbContext>(builder => builder.UseNpgsql(connectionString, options => options.MigrationsAssembly(assembly)), contextLifetime);
        }

        return services;
    }

    /// <summary>
    /// 注册数据库上下文创建对象
    /// </summary>
    /// <param name="builder">依赖注入服务容器</param>
    /// <param name="connectionString">数据库连接字符串</param>
    /// <param name="migrationsAssembly">数据迁移作用的程序集</param>
    /// <returns></returns>
    public static DbContextOptionsBuilder Build(this DbContextOptionsBuilder builder, string? connectionString = default, string? migrationsAssembly = default)
    {
        if (!string.IsNullOrWhiteSpace(connectionString))
        {
            var assembly = migrationsAssembly ?? Assembly.GetCallingAssembly().FullName;
            return builder.UseNpgsql(connectionString, options => options.MigrationsAssembly(assembly));
        }

        return builder;
    }

    /// <summary>
    /// 迁移目标数据库表数据
    /// </summary>
    /// <typeparam name="TSourceDbContext"></typeparam>
    /// <typeparam name="TTargetDbContext"></typeparam>
    /// <typeparam name="TEntity"></typeparam>
    /// <param name="sourceDbContext"></param>
    /// <param name="targetDbContext"></param>
    /// <returns></returns>
    public static async Task MigrationAsync<TSourceDbContext, TTargetDbContext, TEntity>(this TSourceDbContext sourceDbContext, TTargetDbContext targetDbContext) where TSourceDbContext : DbContext where TTargetDbContext : DbContext where TEntity : class
    {
        var data = await sourceDbContext.Set<TEntity>().AsNoTracking().ToListAsync();
        var total = data.Count;
        if (total != 0)
        {
            await targetDbContext.InsertsAsync(data);
            //await targetDbContext.AddRangeAsync(data);
            //await targetDbContext.SaveChangesAsync();
            data.Clear();
        }
    }

    /// <summary>
    /// 批量插入pgsql数据库
    /// </summary>
    /// <typeparam name="TEntity">要写入的数据类型</typeparam>
    /// <param name="data">要写入的数据</param>
    /// <returns></returns>
    public static async Task<ulong> InsertsAsync<TDbContext, TEntity>(this TDbContext context, IEnumerable<TEntity> data) where TDbContext : DbContext
    {
        if (data is null || !data.Any())
        {
            return 0;
        }

        ulong result = 0;
        var connection = context.Database.GetDbConnection();
        if (connection is NpgsqlConnection npgsqlConnection)
        {
            if (npgsqlConnection.State == ConnectionState.Closed)
            {
                await npgsqlConnection.OpenAsync();
            }

            var type = typeof(TEntity);
            var propertyInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => (x.PropertyType.IsValueType || x.PropertyType == typeof(string)) && !x.HasAttribute<NotMappedAttribute>());
            var columns = propertyInfos.Select(x => x.GetColumnName()).ToString(",", "\"{0}\"");
            var copySql = $"COPY \"{type.GetTableName()}\" ({columns}) FROM STDIN (FORMAT BINARY)";

            using var writer = npgsqlConnection.BeginBinaryImport(copySql);
            foreach (var t in data)
            {
                await writer.StartRowAsync();
                foreach (var propertyInfo in propertyInfos)
                {
                    var value = propertyInfo.GetValue(t);
                    var propertyType = propertyInfo.PropertyType.GetCoreType();
                    if (value is null && (propertyInfo.PropertyType.HasAttribute<RequiredAttribute>() || (propertyType != typeof(string) && !propertyInfo.PropertyType.IsNullable())))
                    {
                        throw new Exception($"The type of {type.GetDisplayName()} property {propertyInfo.Name} can not be null!");
                    }

                    if (value is null)
                    {
                        await writer.WriteNullAsync();
                        continue;
                    }

                    if (propertyType.IsEnum)
                    {
                        var columnType = propertyInfo.GetAttributeValue<ColumnAttribute, string>(nameof(ColumnAttribute.TypeName));
                        if (!string.IsNullOrWhiteSpace(columnType) && columnType.StartsWith("varchar", StringComparison.InvariantCultureIgnoreCase))
                        {
                            await writer.WriteAsync(Enum.Format(propertyType, value, "F"));
                        }
                        else
                        {
                            //TODO TBT 数值类型的枚举值待验证
                            await writer.WriteAsync((int)value);
                        }

                        continue;
                    }

                    var typeCode = Type.GetTypeCode(propertyType);
                    if (typeCode == TypeCode.Boolean && value is bool boolValue)
                    {
                        await writer.WriteAsync(boolValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Byte && value is byte byteValue)
                    {
                        await writer.WriteAsync(byteValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Char && value is char charValue)
                    {
                        await writer.WriteAsync(charValue);
                        continue;
                    }

                    if (typeCode == TypeCode.DateTime && value is DateTime dateTimeValue)
                    {
                        // TODO 设定为仅支持 UTC 时间的时间戳的方式
                        //await writer.WriteAsync(DateTime.SpecifyKind(dateTimeValue, DateTimeKind.Local));
                        await writer.WriteAsync(dateTimeValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Decimal && value is decimal decimalValue)
                    {
                        await writer.WriteAsync(decimalValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Double && value is double doubleValue)
                    {
                        await writer.WriteAsync(doubleValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Int16 && value is short shortValue)
                    {
                        await writer.WriteAsync(shortValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Int32 && value is int intValue)
                    {
                        await writer.WriteAsync(intValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Int64 && value is long longValue)
                    {
                        await writer.WriteAsync(longValue);
                        continue;
                    }

                    if (typeCode == TypeCode.SByte && value is sbyte sbyteValue)
                    {
                        await writer.WriteAsync(sbyteValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Single && value is float floatValue)
                    {
                        await writer.WriteAsync(floatValue);
                        continue;
                    }

                    if (typeCode == TypeCode.UInt16 && value is ushort ushortValue)
                    {
                        await writer.WriteAsync(ushortValue);
                        continue;
                    }

                    if (typeCode == TypeCode.UInt32 && value is uint uintValue)
                    {
                        await writer.WriteAsync(uintValue);
                        continue;
                    }

                    if (typeCode == TypeCode.UInt64 && value is ulong ulongValue)
                    {
                        await writer.WriteAsync(ulongValue);
                        continue;
                    }

                    if (typeCode == TypeCode.String && value is string stringValue)
                    {
                        await writer.WriteAsync(stringValue);
                        continue;
                    }

                    if (typeCode == TypeCode.Object)
                    {
                        if (value is DateOnly dateOnly)
                        {
                            await writer.WriteAsync(dateOnly);
                            continue;
                        }

                        if (value is TimeOnly timeOnly)
                        {
                            await writer.WriteAsync(timeOnly);
                            continue;
                        }

                        if (value is DateTimeOffset dateTimeOffset)
                        {
                            // TODO 设定为仅支持 UTC 时间的时间戳的方式
                            //await writer.WriteAsync(DateTime.SpecifyKind(dateTimeOffset, DateTimeKind.Local));
                            await writer.WriteAsync(dateTimeOffset);
                            continue;
                        }

                        if (value is ICollection collection)
                        {
                            await writer.WriteAsync(collection);
                            continue;
                        }
                    }

                    await writer.WriteAsync(AppSettings.CHAR_QUOTE + value.ToString() + AppSettings.CHAR_QUOTE);
                }
            }

            result = await writer.CompleteAsync();
        }

        return result;
    }
}